/*
 * Copyright 2007 Stefan Arentz <http://stefan.arentz.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sateh.spaces.simple;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * This class implements the space implementation. It is Stefan's  first naive try to implement his own flavour of
 * the JavaSpaces specification.
 *
 * @author Stefan Arentz <stefan@arentz.nl>
 */

public class SimpleSpace
{
    public SimpleSpace()
    {
    }

    private UUID id = UUID.randomUUID();

    public UUID getId()
    {
        return id;
    }

    /**
     *
     */

    private final Set<SimpleSpaceObjectWaiter> objectWaiters = new HashSet<SimpleSpaceObjectWaiter>();

    /**
     *
     */
    
    private final SimpleSpaceObjectStore objectStore = new SimpleSpaceObjectStore();

    /**
     *
     */

    UUID writeSpaceObject(SimpleSpaceObject object, long duration)
    {
        boolean objectNeedsToGoInSpace = true;

        synchronized (this)
        {
            // First see if there was an object waiter waiting for this object. If so then the object
            // never really enters the space, we simply push it back to the one waiting for it.

            for (SimpleSpaceObjectWaiter waiter : objectWaiters)
            {
                if (waiter.getMatcher().willMatch(object))
                {
                    waiter.put(object);
                    objectWaiters.remove(waiter);

                    // Don't put the object in the space if a 'taker' was waiting for it

                    if (waiter.getType() == SimpleSpaceObjectWaiter.TAKER) {
                        objectNeedsToGoInSpace = false;
                    }

                    break;
                }
            }
        }

        // If there was no waiter for this object then add it to the space.

        if (objectNeedsToGoInSpace) {
            objectStore.storeObject(object);
        }

        // Return the object id no matter what. Even if the object is taken directly by a waiter, we
        // still return a Lease object containing this object id to the client. TODO Is that right?

        return object.getId();
    }

    /**
     *
     */

    public SimpleSpaceObject takeOrWaitForSpaceObject(String objectClass, long timeout)
    {
        SimpleSpaceObjectWaiter waiter;

        synchronized (this)
        {
            // First we see if there is an object of the requested class available. This is not very
            // efficient but we can always refactor that later on.

            ObjectMatcher matcher = new SimpleSpaceObjectClassMatcher(objectClass);

            SimpleSpaceObject object = objectStore.matchAndTakeObject(matcher);
            if (object != null) {
                return object;
            }

            // If not then we create a object waiter. The waiter is a combination of a matcher and a
            // blocking queue. The main object adder thread will push all objects through all the
            // registered matchers and in case of a succesful willMatch unlock the wait on the queue so
            // that the object will be retrieved by the waiter.

            waiter = new SimpleSpaceObjectWaiter(matcher, SimpleSpaceObjectWaiter.TAKER);
            objectWaiters.add(waiter);
        }

        // Wait for an object to become available.
        // TODO Can this be refactored with goodness from java.util.concurrent?

        SimpleSpaceObject object = waiter.poll(timeout);
        if (object != null) {
            return object;
        } else {
            return null;
        }
    }

    /**
     *
     */

    public SimpleSpaceObject takeSpaceObject(String objectClass)
    {
        ObjectMatcher matcher = new SimpleSpaceObjectClassMatcher(objectClass);

        SimpleSpaceObject object = objectStore.matchAndTakeObject(matcher);
        if (object != null) {
            return object;
        }

        return null;
    }

    /**
     *
     */

    public SimpleSpaceObject takeSpaceObject(String objectClass, SimpleSpaceTemplate template)
    {
        ObjectMatcher matcher = new SimpleSpaceObjectTemplateMatcher(template);

        SimpleSpaceObject object = objectStore.matchAndTakeObject(matcher);
        if (object != null) {
            return object;
        }

        return null;
    }

    /**
     *
     */

    public SimpleSpaceObject takeOrWaitForSpaceObject(String objectClass, SimpleSpaceTemplate templateProperties, long timeout)
    {
        SimpleSpaceObjectWaiter waiter;

        synchronized (this)
        {
            // First we see if there is an object of the requested class available. This is not very
            // efficient but we can always refactor that later on.

            ObjectMatcher matcher = new SimpleSpaceObjectTemplateMatcher(templateProperties);

            SimpleSpaceObject object = objectStore.matchAndTakeObject(matcher);
            if (object != null) {
                return object;
            }

            // If not then we create a object waiter. The waiter is a combination of a matcher and a
            // blocking queue. The main object adder thread will push all objects through all the
            // registered matchers and in case of a succesful willMatch unlock the wait on the queue so
            // that the object will be retrieved by the waiter.

            waiter = new SimpleSpaceObjectWaiter(matcher, SimpleSpaceObjectWaiter.TAKER);
            objectWaiters.add(waiter);
        }

        // Wait for an object to become available.
        // TODO Can this be refactored with goodness from java.util.concurrent?

        SimpleSpaceObject object = waiter.poll(timeout);
        if (object != null) {
            return object;
        } else {
            return null;
        }
    }

    /**
     *
     */

    public SimpleSpaceObject readOrWaitForSpaceObject(String className, long timeout)
    {
        SimpleSpaceObjectWaiter waiter;

        synchronized (this)
        {
            // First we see if there is an object of the requested class available. This is not very
            // efficient but we can always refactor that later on.

            ObjectMatcher matcher = new SimpleSpaceObjectClassMatcher(className);

            SimpleSpaceObject object = objectStore.matchAndReadObject(matcher);
            if (object != null) {
                return object;
            }

            // If not then we create a object waiter. The waiter is a combination of a matcher and a
            // blocking queue. The main object adder thread will push all objects through the matcher
            // and in case of a willMatch unlock the wait on the queue.

            waiter = new SimpleSpaceObjectWaiter(matcher, SimpleSpaceObjectWaiter.READER);
            objectWaiters.add(waiter);
        }

        // Wait for an object to become available.

        SimpleSpaceObject object = waiter.poll(timeout);

        if (object != null) {
            return object;
        } else {
            return null;
        }
    }
    
    /**
     *
     */

    public SimpleSpaceObject readSpaceObject(String objectClass)
    {
        synchronized (this)
        {
            ObjectMatcher matcher = new SimpleSpaceObjectClassMatcher(objectClass);

            SimpleSpaceObject object = objectStore.matchAndReadObject(matcher);
            if (object != null) {
                return object;
            }
        }

        return null;
    }

    /**
     *
     */

    public SimpleSpaceObject readSpaceObject(String objectClass, SimpleSpaceTemplate template)
    {
        synchronized (this)
        {
            ObjectMatcher matcher = new SimpleSpaceObjectTemplateMatcher(template);

            SimpleSpaceObject object = objectStore.matchAndReadObject(matcher);
            if (object != null) {
                return object;
            }
        }

        return null;
    }

    /**
     *
     */

    public SimpleSpaceObject readOrWaitForSpaceObject(SimpleSpaceTemplate template, long timeout)
    {
        SimpleSpaceObjectWaiter waiter;

        synchronized (this)
        {
            // First we see if there is an object of the requested class available. This is not very
            // efficient but we can always refactor that later on.

            ObjectMatcher matcher = new SimpleSpaceObjectTemplateMatcher(template);

            SimpleSpaceObject object = objectStore.matchAndReadObject(matcher);
            if (object != null) {
                return object;
            }

            // If not then we create a object waiter. The waiter is a combination of a matcher and a
            // blocking queue. The main object adder thread will push all objects through the matcher
            // and in case of a willMatch unlock the wait on the queue.

            waiter = new SimpleSpaceObjectWaiter(matcher, SimpleSpaceObjectWaiter.READER);
            objectWaiters.add(waiter);
        }

        // Wait for an object to become available.

        SimpleSpaceObject object = waiter.poll(timeout);

        if (object != null) {
            return object;
        } else {
            return null;
        }
    }
    
    /**
     *
     */
    
    public boolean cancelLeaseForObjectWithId(UUID objectId)
    {
        return objectStore.cancelObjectLease(objectId);
    }

    /**
     *
     */
    
    public boolean renewLeaseForObjectWithId(UUID objectId, long timeToLive)
    {
        return objectStore.renewObjectLease(objectId, timeToLive);
    }

    //

    /**
     *
     */

    public long count(String className)
    {
        ObjectMatcher matcher = new SimpleSpaceObjectClassMatcher(className);
        return objectStore.matchAndCount(matcher);
    }

    /**
     *
     */
    
    public long count(SimpleSpaceTemplate template)
    {
        ObjectMatcher matcher = new SimpleSpaceObjectTemplateMatcher(template);
        return objectStore.matchAndCount(matcher);
    }
}
